using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using NUnit.Framework;
using ProjectEuler.Core.Helpers;

namespace ProjectEuler.Core
{
    public class Problem65 : IProjectEulerProblem
    {
        public int Number
        {
            get { return 65; }
        }

        public string Description
        {
            get { return "Find the sum of digits in the numerator of the 100th convergent of the continued fraction for e."; }
        }

        public string Answer
        {
            get { return ConvergentE(100).Numerator.ToString().SumDigits().ToString(); }
        }

        protected Fraction ConvergentE(int n)
        {
            var eseq = Eseq(n);
            var firstConvergent = new Fraction(eseq[0], 1);
            if (n == 1) return firstConvergent;
            var nthConvergent = new Fraction(1, eseq[n-1]);
            for (int i = n-2; i > 0; i--)
            {
                nthConvergent.AddTo(new Fraction(eseq[i], 1));
                nthConvergent.Reciprocate();
            }
            firstConvergent.AddTo(nthConvergent);
            return firstConvergent;
        }

        protected int[] Eseq(int maxindex)
        {
            var e = new List<int>();
            e.Add(2);
            e.Add(1);
            int k = 1;
            while(e.Count < maxindex)
            {
                e.Add(2 * k);
                e.Add(1);
                e.Add(1);
                k++;
            }
            return e.Take(maxindex).ToArray();
        }
    }

    [TestFixture]
    public class Problem65Tests : Problem65
    {
        [Test]
        public void Can_get_e_number_sequence()
        {
            Assert.That(Eseq(1000)[0], Is.EqualTo(2));
            Assert.That(Eseq(1000)[1], Is.EqualTo(1));
            Assert.That(Eseq(1000)[2], Is.EqualTo(2));
            Assert.That(Eseq(1000)[3], Is.EqualTo(1));
            Assert.That(Eseq(1000)[4], Is.EqualTo(1));
            Assert.That(Eseq(1000)[5], Is.EqualTo(4));
            Assert.That(Eseq(1000)[6], Is.EqualTo(1));
            Assert.That(Eseq(1000)[7], Is.EqualTo(1));
            Assert.That(Eseq(1000)[8], Is.EqualTo(6));
            Assert.That(Eseq(1000)[9], Is.EqualTo(1));
            Assert.That(Eseq(1000)[10], Is.EqualTo(1));
        }

        [Test]
        public void Can_add_fractions()
        {
            var f1 = new Fraction(1,4);
            var f2 = new Fraction(1, 1);
            f1.AddTo(f2);
            Assert.That(f1.Numerator, Is.EqualTo(new BigInteger(5)));
            Assert.That(f1.Denominator, Is.EqualTo(new BigInteger(4)));
        }

        [Test]
        public void Can_reciprocate_fractions()
        {
            var f1 = new Fraction(5, 4);
            f1.Reciprocate();
            Assert.That(f1.Numerator, Is.EqualTo(new BigInteger(4)));
            Assert.That(f1.Denominator, Is.EqualTo(new BigInteger(5)));
        }

        [Test]
        public void Can_get_first_convergent()
        {
            var firstConvergent = ConvergentE(1);
            Assert.That(firstConvergent.Numerator, Is.EqualTo(new BigInteger(2)));
            Assert.That(firstConvergent.Denominator, Is.EqualTo(new BigInteger(1)));
        }

        [Test]
        public void Can_get_second_convergent()
        {
            var secondConvergent = ConvergentE(2);
            Assert.That(secondConvergent.Numerator, Is.EqualTo(new BigInteger(3)));
            Assert.That(secondConvergent.Denominator, Is.EqualTo(new BigInteger(1)));
        }

        [Test]
        public void Can_get_third_convergent()
        {
            var thirdConvergent = ConvergentE(3);
            Assert.That(thirdConvergent.Numerator, Is.EqualTo(new BigInteger(8)));
            Assert.That(thirdConvergent.Denominator, Is.EqualTo(new BigInteger(3)));
        }
        
        [Test]
        public void Can_get_fourth_convergent()
        {
            var thirdConvergent = ConvergentE(4);
            Assert.That(thirdConvergent.Numerator, Is.EqualTo(new BigInteger(11)));
            Assert.That(thirdConvergent.Denominator, Is.EqualTo(new BigInteger(4)));
        }

        [Test]
        public void Can_get_fifth_convergent()
        {
            var thirdConvergent = ConvergentE(5);
            Assert.That(thirdConvergent.Numerator, Is.EqualTo(new BigInteger(19)));
            Assert.That(thirdConvergent.Denominator, Is.EqualTo(new BigInteger(7)));
        }

        [Test]
        public void The_sum_of_digits_in_the_numerator_of_the_10th_convergent_is_17()
        {
            var tenthConvergent = ConvergentE(10);
            var sumNumatorDigits = tenthConvergent.Numerator.ToString().SumDigits();
            Assert.That(sumNumatorDigits, Is.EqualTo(17));
        }
    }
}